using System;
using System.Diagnostics;
using System.IO;
using System.Collections;
using System.Text;

using ICSharpCode.SharpZipLib.Zip.Compression.Streams;

namespace MSHelpLib.BaseCode
{
	/// <summary>
	/// Summary description for BaseHelpFile.
	/// </summary>
	public abstract class BaseHelpFile : IHelpFile
	{
		/// <summary>
		/// Internal member storing a reference to the hosting HXSHelpSystem instance
		/// </summary>
		protected IHelpSystem _systemInstance = null;

		/// <summary>
		/// Internal flag specifying if the object is going to be disposed
		/// </summary>
		protected bool disposed = false;
		/// <summary>
		/// 
		/// Internal arraylist containing the table of contents
		/// </summary>
		protected  ArrayList _toc = null;
		
		/// <summary>
		/// Internal arraylist containing the index (klinks)
		/// </summary>
		protected ArrayList _indexKLinks = null;
		
		/// <summary>
		/// Internal arraylist containing the index (alinks)
		/// </summary>
		protected ArrayList _indexALinks = null;
		
		/// <summary>
		/// Gets the internal toc read from the text-based hhc file
		/// </summary>
		public ArrayList TOC
		{
			get 
			{ 
				if (_toc==null)
				{                                
					if (this.m_FATOC!=null)
					{
						_toc=new ArrayList();
						PartFileStream pfs=m_FileArchive.ExtractFile(m_FATOC);
						InflaterInputStream st=new InflaterInputStream(pfs);
						BinaryReader br=new BinaryReader(st);
						br=new BinaryReader(st);
						Int32 TOCCount=br.ReadInt32();
						for(int i=0;i<TOCCount;i++)
						{
							TOCItem item=new TOCItem();
							item.LoadFromStream(br,null,this);
							_toc.Add(item);
						}
						pfs.Close();
					}
				}
				return _toc; 
			}
		}

		/// <summary>
		/// Gets the internal index read from the chm file.
		/// </summary>
		public ArrayList IndexKLinks
		{
			get { return _indexKLinks; }
		}

		/// <summary>
		/// Gets the internal index read from the chm file.
		/// </summary>
		public ArrayList IndexALinks
		{
			get { return _indexALinks; }
		}

		/// <summary>
		/// Internal member storing the default encoder
		/// </summary>
		protected Encoding _textEncoding = Encoding.GetEncoding(1252); // standard windows-1252 encoder
		/// <summary>
		/// Gets/Sets the text encoding
		/// </summary>
		public Encoding TextEncoding
		{
			get { return _textEncoding; }
			set { _textEncoding = value; }
		}

        /// <summary>
        /// 
        /// </summary>
		protected FileInfo	m_FInfo=null;
        /// <summary>
        /// Gets the F info.
        /// </summary>
        /// <value>The F info.</value>
		public FileInfo		FInfo
		{
			get
			{
				return m_FInfo;
			}
		}

        /// <summary>
        /// 
        /// </summary>
		protected string _FileName="";
        /// <summary>
        /// Gets the name of the file.
        /// </summary>
        /// <value>The name of the file.</value>
		public string FileName
		{
			get
			{
				if (m_FInfo!=null)
				{
					return m_FInfo.FullName;
				}
				return "";
			}
		}

        /// <summary>
        /// 
        /// </summary>
		protected IHelpSystem m_HelpSystem=null;
        /// <summary>
        /// Gets or sets the help system.
        /// </summary>
        /// <value>The help system.</value>
		public IHelpSystem HelpSystem
		{
			get { return m_HelpSystem;}
			set { m_HelpSystem=value;}			
		}

        /// <summary>
        /// 
        /// </summary>
		protected Search m_Search=null;

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseHelpFile"/> class.
        /// </summary>
		public BaseHelpFile()
		{			
		}

        /// <summary>
        /// Loads the dump.
        /// </summary>
        /// <param name="helpFile">The help file.</param>
        /// <returns></returns>
		public bool LoadDump(IHelpFile helpFile)
		{
			if (File.Exists(Path.ChangeExtension(FInfo.FullName,".msh")))
			{
				return true;
			}
			return false;
//				// Load Index & TOC from file.
//				try
//				{					
//					FileArchive fa=new FileArchive(Path.ChangeExtension(FInfo.FullName,".msh"));
//					
//					PartFileStream pfs=fa.ExtractFile("Info");
//					BinaryReader br=new BinaryReader(pfs);
//					string Version=br.ReadString();
//					string HelpName=br.ReadString();
//
//					// Read TOC
//					FileArchiveEntry entry=fa.FindFile("TOC");
//					if (entry!=null)
//					{
//						pfs=fa.ExtractFile(entry);
//						InflaterInputStream st=new InflaterInputStream(pfs);
//						br=new BinaryReader(st);
//						Int32 TOCCount=br.ReadInt32();
//						for(int i=0;i<TOCCount;i++)
//						{
//							TOCItem item=new TOCItem();
//							item.LoadFromStream(br,null,helpFile);
//							_toc.Add(item);
//						}
//						pfs.Close();
//					}	
//
//					// Read Index
//					entry=fa.FindFile("Index");
//					if (entry!=null)
//					{
//						pfs=fa.ExtractFile(entry);
//						InflaterInputStream st=new InflaterInputStream(pfs);
//						br=new BinaryReader(st);
//						Int32 IndexCount=br.ReadInt32();
//					
//						for(int i=0;i<IndexCount;i++)
//						{							
//							IndexItem2 item=new IndexItem2();						
//							item.LoadFromStream(br,helpFile);						
//							_indexKLinks.Add(item);
//						}
//						pfs.Close();
//					}	
//				
//					// Read Search Index
//					entry=fa.FindFile("Search");					
//					if (entry!=null)				
//					{
//						pfs=fa.ExtractFile(entry);
//						InflaterInputStream st=new InflaterInputStream(pfs);
//						br=new BinaryReader(st);
//						m_Search=new Search();
//						m_Search.LoadDump(br,helpFile);
//						pfs.Close();
//					}				
//					
//					OnProgress(0,100);
//					return true;
//				}
//				catch(Exception ex)
//				{
//					Debug.WriteLine(ex.Message);
//					return false;
//				}
//			}
//			else
//				return false;
		}

        /// <summary>
        /// Saves the dump.
        /// </summary>
		public void SaveDump()
		{			
			// Save Index & TOC from file.
			string FileName="";
			FileName=Path.ChangeExtension(FInfo.FullName,".msh");			
			try
			{
			// 	FileArchive fa=new FileArchive(FileName);				
				FileArchiveEntry Entry=null;

				MemoryStream memFile=null;
				BinaryWriter bw=null;

				if (m_FileArchive==null)
					m_FileArchive=new FileArchive(m_FileArchiveFileName);

				Entry=m_FileArchive.FindFile("Info");
				if (Entry==null)
				{
					memFile=new MemoryStream();
					bw=new BinaryWriter(memFile);
					Version Ver=new Version(1,0,0,1);
					bw.Write(Ver.ToString());
					bw.Write(this.FInfo.FullName);
					m_FileArchive.AddFile("Info",memFile);
					memFile.Close();
				}
								
				// Write TOC
				Entry=m_FileArchive.FindFile("TOC");
				if (Entry==null)
				{
					if ((_toc!=null) && (_toc.Count>0))
					{						
						memFile=new MemoryStream();
						DeflaterOutputStream gzipStream=new DeflaterOutputStream(memFile);
						bw=new BinaryWriter(gzipStream);

						bw.Write((Int32)_toc.Count);
						foreach(TOCItem item in _toc)
						{						
							item.SaveToStream(bw);
						}					
						gzipStream.Finish();
						gzipStream.Flush();
						memFile.Flush();
						m_FileArchive.AddFile("TOC",memFile);
						memFile.Close();
					}				
				}

				// Write Index
				Entry=m_FileArchive.FindFile("Index");
				if (Entry==null)
				{
					if ((this._indexKLinks!=null) && (_indexKLinks.Count>0))
					{
						memFile=new MemoryStream();
						DeflaterOutputStream gzipStream=new DeflaterOutputStream(memFile);
						bw=new BinaryWriter(gzipStream);

						bw.Write((Int32)_indexKLinks.Count);
						foreach(IndexItem2 item in _indexKLinks)
						{
							item.SaveToStream(bw);
						}
						gzipStream.Finish();
						gzipStream.Flush();
						m_FileArchive.AddFile("Index",memFile);
						memFile.Close();
					}
				}

				// Write Search Index
				Entry=m_FileArchive.FindFile("Search");
				if (Entry==null)
				{
					if (m_Search!=null)
					{
						memFile=new MemoryStream();
						DeflaterOutputStream gzipStream=new DeflaterOutputStream(memFile);
						bw=new BinaryWriter(gzipStream);
						
						m_Search.SaveDump(bw);
						gzipStream.Finish();
						gzipStream.Flush();

						m_FileArchive.AddFile("Search",memFile);
						memFile.Close();
					}
				}
			}
			catch(Exception ex)
			{
				Debug.WriteLine(ex.Message);
                try
                {
                    if (File.Exists(FileName))
                        File.Delete(FileName);
                }
                catch
                {
                }
			}			
		}

        /// <summary>
        /// Occurs when [status handler event].
        /// </summary>
		public event MSHelpLib.StatusHandler StatusHandlerEvent=null;
        /// <summary>
        /// Occurs when [progress handler event].
        /// </summary>
		public event MSHelpLib.ProgressHandler ProgressHandlerEvent=null;

		private string			 m_FileArchiveFileName="";
        /// <summary>
        /// 
        /// </summary>
		public	  FileArchive		 m_FileArchive=null;
        /// <summary>
        /// 
        /// </summary>
		public	  FileArchiveEntry m_FATOC=null;
        /// <summary>
        /// 
        /// </summary>
		public	  FileArchiveEntry m_FAIndex=null;
        /// <summary>
        /// 
        /// </summary>
		public	  FileArchiveEntry m_FASearch=null;

        /// <summary>
        /// 
        /// </summary>
		protected bool m_onlySystemData=false;
        /// <summary>
        /// 
        /// </summary>
		protected int Flags=0;
        /// <summary>
        /// Opens the specified system instance.
        /// </summary>
        /// <param name="systemInstance">The system instance.</param>
        /// <param name="helpFileName">Name of the help file.</param>
        /// <param name="_flags">The _flags.</param>
        /// <param name="SH">The SH.</param>
        /// <param name="PH">The PH.</param>
        /// <returns></returns>
		public virtual bool Open(IHelpSystem systemInstance, string helpFileName, int _flags, MSHelpLib.StatusHandler SH, MSHelpLib.ProgressHandler PH)
		{
			Flags=_flags;
			this.m_HelpSystem=systemInstance;
			this.StatusHandlerEvent=SH;
			this.ProgressHandlerEvent=PH;			
			this._systemInstance=m_HelpSystem;

			if ((Flags & (int)LoadFlags.LoadOnlySystemData)!=0)
				m_onlySystemData=true;
			else
				m_onlySystemData=false;
			
			m_FInfo=new FileInfo(helpFileName);			
			if (!m_FInfo.Exists)
				return false;				

			m_FileArchiveFileName=Path.ChangeExtension(m_FInfo.FullName,".msh");
			if (File.Exists(m_FileArchiveFileName))
			{
				m_FileArchive=new FileArchive(m_FileArchiveFileName);
				m_FATOC=m_FileArchive.FindFile("TOC");
				m_FAIndex=m_FileArchive.FindFile("Index");
				m_FASearch=m_FileArchive.FindFile("Search");
			}

			return true;
		}

        /// <summary>
        /// Closes this instance.
        /// </summary>
		public virtual void Close()
		{			
			this.BaseStream.Close();
		}
		
		private int MaxValue=100;
        /// <summary>
        /// Called when [progress].
        /// </summary>
        /// <param name="Value">The value.</param>
        /// <param name="_MaxValue">The _ max value.</param>
		public virtual void OnProgress(int Value, int _MaxValue)
		{			
			MaxValue=_MaxValue;
			if (ProgressHandlerEvent!=null)
				ProgressHandlerEvent(Value,MaxValue);
		}

		private int Level=0;
        /// <summary>
        /// Called when [status].
        /// </summary>
        /// <param name="_Level">The _ level.</param>
        /// <param name="Text">The text.</param>
		public virtual void OnStatus(int _Level, string Text)
		{
			Level=_Level;
			if (StatusHandlerEvent!=null)
				StatusHandlerEvent(Level, Text);
		}

        /// <summary>
        /// Called when [status].
        /// </summary>
        /// <param name="Text">The text.</param>
		public virtual void OnStatus(string Text)
		{
			OnStatus(0,Text);
		}

        /// <summary>
        /// Called when [progress].
        /// </summary>
        /// <param name="Value">The value.</param>
		public virtual void OnProgress(int Value)
		{
			OnProgress(Value,MaxValue);
		}

        /// <summary>
        /// Gets a value indicating whether this instance has table of contents.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has table of contents; otherwise, <c>false</c>.
        /// </value>
		public virtual bool HasTableOfContents
		{
			get
			{
				if ((this.TOC!=null) && (TOC.Count>0))
					return true;
				else
				{
					if (this.m_FATOC!=null)
						return true;
					else
						return false;
				}
			}
		}

		private TableOfContents m_TableOfContents=null;
        /// <summary>
        /// Gets the table of contents.
        /// </summary>
        /// <value>The table of contents.</value>
		public virtual TableOfContents TableOfContents
		{
			get
			{				
				if (m_TableOfContents==null)
				{
					if (HasTableOfContents)
						m_TableOfContents=new TableOfContents(TOC);
				}
				return m_TableOfContents;
			}
		}

        /// <summary>
        /// Gets a value indicating whether this instance has index.
        /// </summary>
        /// <value><c>true</c> if this instance has index; otherwise, <c>false</c>.</value>
		public virtual bool HasIndex
		{
			get
			{
				if (_indexKLinks!=null)
				{
					if (_indexKLinks.Count>0)
						return true;				
				}
				if (_indexALinks!=null)
				{
					if (_indexALinks.Count>0)
						return true;				
				}
				if (m_FAIndex!=null)
					return true;
				return false;
			}
		}

		private Index _index=null;
        /// <summary>
        /// Gets the index.
        /// </summary>
        /// <value>The index.</value>
		public virtual Index Index
		{
			get
			{
				if (_index==null)
				{
					if (HasIndex)
					{
						if ((_indexKLinks==null) && (_indexALinks==null))
						{
							if (m_FAIndex!=null)
							{
								PartFileStream pfs=m_FileArchive.ExtractFile(m_FAIndex);
								InflaterInputStream st=new InflaterInputStream(pfs);
								BinaryReader br=new BinaryReader(st);
								Int32 IndexCount=br.ReadInt32();
								_indexKLinks=new ArrayList();
					
								for(int i=0;i<IndexCount;i++)
								{							
									IndexItem2 item=new IndexItem2();						
									item.LoadFromStream(br,this);						
									_indexKLinks.Add(item);
								}
								pfs.Close();
							}
						}
						_index=new Index(_indexKLinks,_indexALinks);
					}
				}
				return _index;
			}
		}

        /// <summary>
        /// Gets the type of the help index.
        /// </summary>
        /// <value>The type of the help index.</value>
		public virtual MSHelpLib.IndexType HelpIndexType
		{
			get
			{
				if (_index==null)
					return IndexType.None;
				else
				{
					if (_index.KLinks.Count>0)
						return IndexType.KeywordLinks;
					else if (_index.ALinks.Count>0)
						return IndexType.AssociativeLinks;
					else return IndexType.None;
				}
			}
		}

        /// <summary>
        /// 
        /// </summary>
		protected ArrayList _mergeLinks=new ArrayList();
		/// <summary>
		/// Gets an arraylist of TOC items which contains merg-links to other CHMs
		/// </summary>	
		public virtual ArrayList MergeLinks
		{
			get { return _mergeLinks; }
		}

		/// <summary>
		/// URL prefix for specifying a chm destination
		/// </summary>
		protected string _urlPrefix = "ms-help:";

		/// <summary>
		/// Gets/Sets the url prefix for specifying a chm destination
		/// </summary>
		public virtual string UrlPrefix
		{
			get { return _urlPrefix; }			
		}

        /// <summary>
        /// Inits the search.
        /// </summary>
		public virtual void InitSearch()
		{			
			if (m_Search==null)
			{
				if	(m_FASearch!=null)
				{
					// Load data
					this.OnStatus("Loading Search Index for "+this.FInfo.Name);
					PartFileStream pfs=m_FileArchive.ExtractFile(m_FASearch);					
					InflaterInputStream st=new InflaterInputStream(pfs);
					BinaryReader br=new BinaryReader(st);					
					m_Search=new Search(this);
					m_Search.LoadDump(br);
					pfs.Close();
				}
				else
				{
					if ((Flags & (int)(LoadFlags.CreateSearchIndexOnFirstUseAndSave | LoadFlags.CreateSearchIndexOnLoad))!=0)
					{
						this.OnStatus("Building Search Index");
						m_Search=new Search(this);
						m_Search.IndexHelpFile();

						if ((Flags & (int)(LoadFlags.CreateSearchIndexOnFirstUseAndSave))!=0)
							this.SaveDump();						
					}
				}
			}
			this.OnProgress(0,100);			
		}

        /// <summary>
        /// Gets a value indicating whether this instance can search.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can search; otherwise, <c>false</c>.
        /// </value>
		public abstract bool CanSearch { get; }

        /// <summary>
        /// Performs the search.
        /// </summary>
        /// <param name="words">The words.</param>
        /// <returns></returns>
		public virtual SearchResultsTable PerformSearch(string words)
		{
			if (m_Search==null)
				return null;
			SearchResultsTable Hits=m_Search.SearchFor(words);
			return MatchResults(Hits);
		}

        /// <summary>
        /// Matches the results.
        /// </summary>
        /// <param name="Hits">The hits.</param>
        /// <returns></returns>
		public virtual SearchResultsTable MatchResults(SearchResultsTable Hits)
		{
			if (Hits==null)
				return null;

			if (Hits.Rows.Count==0)
				return null;
						
			SearchComparer SearchCMP = new SearchComparer();
			foreach (SearchResultsRow row in Hits)
			{				
				bool bFound = false;
				string Url=row.URL.ToLower();
				if (this._indexKLinks != null)
				{       					
					foreach(IndexItem2 item in this._indexKLinks)
					{
						if (item.Url.ToLower()==Url)
						{
							row.Title = item.Title;
							bFound=true;
							break;
						}

						IndexItem2 item2 = SearchIndex(item.Children,Url);
						if (item2 !=null)
						{
							row.Title = item2.Title;
							bFound=true;
							break;
						}
					}
				}

				if ((this._toc != null) && (bFound==false))
				{
					foreach(TOCItem item in this._toc)
					{
						TOCItem item2=SearchTOC(item.Children,Url);
						if (item2!=null)
						{
							row.Title = item2.Name;
							break;
						}
					}
				}
			}
			Hits.AcceptChanges();
			return Hits;
		}

		private IndexItem2 SearchIndex(ArrayList index, string Url)
		{
			foreach (IndexItem2 item in index)
			{
				if (item.FullURL.ToLower() == Url)
				{
					return item;
				}
				IndexItem2 item2=SearchIndex(item.Children, Url);
				if (item2 != null)
					return item2;
			}
			return null;
		}

		private TOCItem SearchTOC(ArrayList toc, string Url)
		{
			foreach (TOCItem item in toc)
			{
				if (item.Url.ToLower() == Url)
				{
					return item;
				}
				TOCItem item2=SearchTOC(item.Children, Url);
				if (item2 != null)
					return item2;
			}
			return null;
		}

        /// <summary>
        /// Gets the base stream.
        /// </summary>
        /// <value>The base stream.</value>
		public abstract IHelpStream BaseStream { get;}

        /// <summary>
        /// Finds the file.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public virtual IFileInfo FindFile(string FileName)
		{			
			if (BaseStream!=null)
				return BaseStream.FindFile(FileName);
			else
				return null;
		}

        /// <summary>
        /// Extracts the specified file info.
        /// </summary>
        /// <param name="fileInfo">The file info.</param>
        /// <returns></returns>
		public virtual MemoryStream Extract(IFileInfo fileInfo)
		{
			if (BaseStream!=null)
				return BaseStream.Extract(fileInfo);
			else
				return null;
		}

        /// <summary>
        /// Extrs the dact.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public virtual MemoryStream Extract(string FileName)
		{
			IFileInfo fileInfo=BaseStream.FindFile(FileName);
			return BaseStream.Extract(fileInfo);			
		}

        /// <summary>
        /// Extracts as text.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public string ExtractAsText(string FileName)
		{
			IFileInfo fileInfo=BaseStream.FindFile(FileName);
			return ExtractAsText(fileInfo);			
		}

        /// <summary>
        /// Extracts as text.
        /// </summary>
        /// <param name="fileInfo">The file info.</param>
        /// <returns></returns>
		public string ExtractAsText(IFileInfo fileInfo)
		{
			if (fileInfo==null)
				return null;				
						
			if (fileInfo.Length<=0)
				return null;

			string Text="";			
			try
			{
				MemoryStream st=fileInfo.HelpStream.Extract(fileInfo);
				st.Seek(0,SeekOrigin.Begin);
				
				if (st.Length==0)
					return Text;

				long start = 0;
				int Len=(int)st.Length;

				byte [] buf=new byte[4];
				st.Read(buf ,0,3);                
				if ((buf[0] == 0xef) && (buf[1] == 0xbb) && (buf[2] == 0xbf))
				{
					start = 3;
					Len-=3;
				}
				
				ASCIIEncoding ascii=new ASCIIEncoding();
				st.Seek(start,SeekOrigin.Begin);
				
				Text = ascii.GetString(st.ToArray(), 0,  Len);

				// UTF Decoding
				st.Seek(start,SeekOrigin.Begin);
				if (Text.ToUpper().IndexOf("UTF-8")!=-1)
				{
					UTF8Encoding utf8 = new UTF8Encoding();
					Text = utf8.GetString(st.ToArray(), 0, Len);				
				}
				else
					Text = ascii.GetString(st.ToArray(), 0, Len);
			}
			catch(Exception ex)
			{
				Text="##-"+ex.Message;
			}
			return Text;
		}

        /// <summary>
        /// Gets the number of files.
        /// </summary>
        /// <value>The number of files.</value>
		public virtual int NumberOfFiles
		{
			get
			{
				if (BaseStream!=null)
					return BaseStream.NumberOfFiles;
				else
					return 0;
			}
		}

        /// <summary>
        /// Processes all files.
        /// </summary>
        /// <param name="processAllFiles">The process all files.</param>
        /// <returns></returns>
		public virtual int ProcessAllFiles(MSHelpLib.ProcessAllFilesDelegate processAllFiles)
		{
			if (BaseStream!=null)
				return BaseStream.ProcessAllFiles(processAllFiles);
			else
				return 0;
		}

        /// <summary>
        /// Sets the flags.
        /// </summary>
        /// <param name="Flags">The flags.</param>
        public virtual void SetFlags(int Flags)
        {
            this.Flags = Flags;            
        }
	}
}
